/**
 * Copyright (c) 2018-2023, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 *
 * Contains: Implementation of virtio-sound.
 *
 * Change Logs:
 * Date           Author            Notes
 * 2023-07-29     Shiroko           Init
 */

#include "nxos.h"

#ifdef CONFIG_NX_DRIVER_VIRTIO_SOUND

#undef NX_LOG_MOD_NAME
#define NX_LOG_MOD_NAME "VIRTIO-SOUND"

#include "base/log.h"

#define DRV_NAME "virtio sound"
#define DEV_NAME "virtio_sound"

#include "../virtio.h"
#include "virtio_sound.h"

NX_INLINE NX_Error VirtioSoundResponseCodeToErr(NX_U32 code)
{
    switch (code)
    {
    case VIRTIO_SND_S_OK:
        return NX_EOK;
    case VIRTIO_SND_S_BAD_MSG:
        return NX_EINVAL;
    case VIRTIO_SND_S_NOT_SUPP:
        return NX_ENOFUNC;
    case VIRTIO_SND_S_IO_ERR:
        return NX_EIO;
    default:
        return NX_EFAULT;
    }
}

NX_PRIVATE NX_Error VirtioSoundSendControlCommand(
        NX_Device *device, int queue_index,
        void *command, NX_Size command_len, void *result, NX_Size result_len)
{
    NX_ASSERT(result_len >= sizeof(VirtioSoundControlHeader));
    NX_U32      idx[2]; // One for request, one for response.
    VirtioQueue *queue = VirtioQueueGet(device, queue_index);
    while (VirtioQueueDescAllocMany(queue, 2, idx) != NX_EOK);

    VirtioQueueDescFill(queue, idx[0], (NX_U64) ((NX_UArch) NX_Virt2Phy(command)), command_len,
                        VIRTIO_QUEUE_DESC_FLAG_NEXT, idx[1]);
    VirtioQueueDescFill(queue, idx[1], (NX_U64) ((NX_UArch) NX_Virt2Phy(result)), result_len,
                        VIRTIO_QUEUE_DESC_FLAG_WRITE, 0);

    VirtioQueuePutIntoAvailRing(queue, idx[0]);
    VirtioSendNotification(device, queue_index, idx[0]);
    VirtioWaitNotification(device, queue_index, idx[0]);
    VirtioQueueDescFreeMany(queue, idx[0]);

    return VirtioSoundResponseCodeToErr(((VirtioSoundControlHeader *) result)->code);
}

NX_PRIVATE NX_Error VirtioSoundPCMSetParameters(
        NX_Device *device,
        NX_U32 streamId,
        VirtioSoundPCMParameters parameters
)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    if (streamId >= deviceExtension->deviceConfig->streams) return NX_EINVAL;
    if (parameters.buffer_bytes % parameters.period_bytes != 0) return NX_EINVAL;
    if (parameters.channels < deviceExtension->pcmStreamsInfo[streamId].channels_min ||
        parameters.channels > deviceExtension->pcmStreamsInfo[streamId].channels_max)
        return NX_EINVAL;
    if (((1 << parameters.format) & ~deviceExtension->pcmStreamsInfo[streamId].formats) != 0)
        return NX_EINVAL;
    if (((1 << parameters.rate) & ~deviceExtension->pcmStreamsInfo[streamId].rates) != 0)
        return NX_EINVAL;
    if ((parameters.features & ~deviceExtension->pcmStreamsInfo[streamId].features) != 0)
        return NX_EINVAL;

    VirtioSoundRequestPCMSetParams req = {
            .pcmHeader.header.code = VIRTIO_SND_R_PCM_SET_PARAMS,
            .pcmHeader.stream_id = streamId,
            .padding = 0,
            .parameters = parameters
    };
    VirtioSoundControlHeader       resp;
    return VirtioSoundSendControlCommand(
            device, VIRTIO_SOUND_QUEUE_CONTROL,
            &req, sizeof(req), &resp, sizeof(resp));
}

NX_PRIVATE NX_Error VirtioSoundPCMPrepare(
        NX_Device *device,
        NX_U32 streamId
)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    if (streamId >= deviceExtension->deviceConfig->streams) return NX_EINVAL;

    VirtioSoundPCMHeader     req = {
            .header.code = VIRTIO_SND_R_PCM_PREPARE,
            .stream_id = streamId
    };
    VirtioSoundControlHeader resp;
    return VirtioSoundSendControlCommand(
            device, VIRTIO_SOUND_QUEUE_CONTROL,
            &req, sizeof(req), &resp, sizeof(resp));
}

NX_PRIVATE NX_Error VirtioSoundPCMRelease(
        NX_Device *device,
        NX_U32 streamId
)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    if (streamId >= deviceExtension->deviceConfig->streams) return NX_EINVAL;

    VirtioSoundPCMHeader     req = {
            .header.code = VIRTIO_SND_R_PCM_RELEASE,
            .stream_id = streamId
    };
    VirtioSoundControlHeader resp;
    return VirtioSoundSendControlCommand(
            device, VIRTIO_SOUND_QUEUE_CONTROL,
            &req, sizeof(req), &resp, sizeof(resp));
}

NX_PRIVATE NX_Error VirtioSoundPCMStart(
        NX_Device *device,
        NX_U32 streamId
)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    if (streamId >= deviceExtension->deviceConfig->streams) return NX_EINVAL;

    VirtioSoundPCMHeader     req = {
            .header.code = VIRTIO_SND_R_PCM_START,
            .stream_id = streamId
    };
    VirtioSoundControlHeader resp;
    return VirtioSoundSendControlCommand(
            device, VIRTIO_SOUND_QUEUE_CONTROL,
            &req, sizeof(req), &resp, sizeof(resp));
}

NX_PRIVATE NX_Error VirtioSoundPCMStop(
        NX_Device *device,
        NX_U32 streamId
)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    if (streamId >= deviceExtension->deviceConfig->streams) return NX_EINVAL;

    VirtioSoundPCMHeader     req = {
            .header.code = VIRTIO_SND_R_PCM_STOP,
            .stream_id = streamId
    };
    VirtioSoundControlHeader resp;
    return VirtioSoundSendControlCommand(
            device, VIRTIO_SOUND_QUEUE_CONTROL,
            &req, sizeof(req), &resp, sizeof(resp));
}

NX_PRIVATE NX_Error VirtioSoundSendTX(
        NX_Device *device, NX_U32 streamId, void *buffer, NX_Size bufferSize)
{
    NX_U32      idx[3]; // Header -> Buffer -> Status
    VirtioQueue *queue = VirtioQueueGet(device, VIRTIO_SOUND_QUEUE_TX);
    while (VirtioQueueDescAllocMany(queue, 3, idx) != NX_EOK);

    VirtioSoundPCMIOHeader header  = {
            .stream_id = streamId
    };
    VirtioSoundPCMIOStatus *status = NX_MemAlloc(sizeof(VirtioSoundPCMIOStatus));
    if (status == NX_NULL) return NX_ENOMEM;

    VirtioQueueDescFill(queue, idx[0],
                        (NX_U64) ((NX_UArch) NX_Virt2Phy(&header)),
                        sizeof(header),
                        VIRTIO_QUEUE_DESC_FLAG_NEXT,
                        idx[1]);
    VirtioQueueDescFill(queue, idx[1],
                        (NX_U64) ((NX_UArch) buffer),
                        bufferSize,
                        VIRTIO_QUEUE_DESC_FLAG_NEXT,
                        idx[2]);
    VirtioQueueDescFill(queue, idx[2],
                        (NX_U64) ((NX_UArch) NX_Virt2Phy(status)),
                        sizeof(VirtioSoundPCMIOStatus),
                        VIRTIO_QUEUE_DESC_FLAG_WRITE,
                        0);

    VirtioQueuePutIntoAvailRing(queue, idx[0]);
    VirtioSendNotification(device, VIRTIO_SOUND_QUEUE_TX, idx[0]);

    return NX_EOK;
}

NX_PRIVATE NX_Error VirtioSoundQueueCompleteHandler(NX_Device *device, VirtioQueue *queue, NX_U32 idx)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    if (queue->queue_index == VIRTIO_SOUND_QUEUE_TX)
    {
        // Searching for result
        NX_ASSERT((queue->desc[idx].flags & VIRTIO_QUEUE_DESC_FLAG_NEXT) != 0);
        NX_U32 rIdx = queue->desc[idx].next;
        NX_ASSERT((queue->desc[rIdx].flags & VIRTIO_QUEUE_DESC_FLAG_NEXT) != 0);
        rIdx = queue->desc[rIdx].next;
        NX_ASSERT(queue->desc[rIdx].len == sizeof(VirtioSoundPCMIOStatus));
        VirtioSoundPCMIOStatus *status = (VirtioSoundPCMIOStatus *) ((NX_UArch) NX_Phy2Virt(queue->desc[rIdx].addr));
        NX_FifoWrite(deviceExtension->result_buffer, (NX_U8 *) status, sizeof(VirtioSoundPCMIOStatus));
        NX_MemFree(status);
        VirtioQueueDescFreeMany(queue, idx);
    }
    else if (queue->queue_index == VIRTIO_SOUND_QUEUE_EVENT)
    {
        // Received an event, handle it when NXOS has device event system.
        NX_ASSERT(queue->desc[idx].len == sizeof(VirtioSoundEvent));
        // VirtioSoundEvent *event = (VirtioSoundEvent *) (NX_UArch) (NX_Phy2Virt(queue->desc[idx].addr));
        // NX_LOG_D("Event received. Code: 0x%X, Data:0x%X.", event->header.code, event->data);
        // Reuse existed descriptor
        VirtioQueuePutIntoAvailRing(queue, idx);
        VirtioSendNotification(device, VIRTIO_SOUND_QUEUE_EVENT, idx);
    }
    return NX_EOK;
}


NX_PRIVATE void VirtioSoundCleanUp(NX_Device *device);

NX_PRIVATE NX_Error VirtioSoundInit(NX_Driver *driver, NX_Device *device)
{
    NX_Error err                       = NX_EOK;

    // Negotiate features & status
    if ((err = VirtioDeviceInitialize(device, NX_NULL, 0)) != NX_EOK)
        return err;

    // Device-specified initialization
    VirtioQueueAlloc(device, 4); // control, event, tx, rx
    VirtioQueueInit(device, VIRTIO_SOUND_QUEUE_CONTROL, CONFIG_NX_DRIVER_VIRTIO_SPLIT_QUEUE_SIZE);
    VirtioQueueInit(device, VIRTIO_SOUND_QUEUE_EVENT, CONFIG_NX_DRIVER_VIRTIO_SPLIT_QUEUE_SIZE);
    VirtioQueueInit(device, VIRTIO_SOUND_QUEUE_TX, CONFIG_NX_DRIVER_VIRTIO_SPLIT_QUEUE_SIZE);
    VirtioQueueInit(device, VIRTIO_SOUND_QUEUE_RX, CONFIG_NX_DRIVER_VIRTIO_SPLIT_QUEUE_SIZE);
    VirtioQueueRegisterNotifier(VirtioQueueGet(device, VIRTIO_SOUND_QUEUE_TX),
                                VirtioSoundQueueCompleteHandler);
    VirtioQueueRegisterNotifier(VirtioQueueGet(device, VIRTIO_SOUND_QUEUE_EVENT),
                                VirtioSoundQueueCompleteHandler);

    // Driver OK
    VirtioDeviceDriverOK(device);

    VirtioSoundDeviceExtension *deviceExtension = NX_MemAlloc(sizeof(VirtioSoundDeviceExtension));
    if (deviceExtension == NX_NULL)
        goto nomem_failed;
    VirtioGetExtension(device)->devExt = (void *) deviceExtension;
    deviceExtension->pcmStreamsInfo             = NX_NULL;

    VirtioSoundDeviceConfig *deviceCfg = NX_MemAlloc(sizeof(VirtioSoundDeviceConfig));
    if (deviceCfg == NX_NULL)
        goto nomem_failed;

    VirtioReadDeviceConfig(device, 0, deviceCfg, sizeof(VirtioSoundDeviceConfig));
    deviceExtension->deviceConfig = deviceCfg;

    VirtioSoundRequestQueryInfoHeader queryInfo;
    NX_Size                           infoRespSize = 0;
    VirtioSoundControlHeader          *infoResp    = NX_NULL;
    // Read the jacks field and send a control request to query information about the available jacks.
    if (deviceCfg->jacks != 0)
    {
        queryInfo.header.code = VIRTIO_SND_R_JACK_INFO;
        queryInfo.start_id    = 0;
        queryInfo.size        = sizeof(VirtioSoundResponseQueryJackInfo);
        queryInfo.count       = deviceCfg->jacks;
        infoRespSize = queryInfo.size * queryInfo.count + sizeof(VirtioSoundControlHeader);
        infoResp     = NX_MemReAlloc(infoResp, infoRespSize);
        if (infoResp == NX_NULL)
            goto nomem_failed;
        if ((err   = VirtioSoundSendControlCommand(
                device, VIRTIO_SOUND_QUEUE_CONTROL,
                &queryInfo, sizeof(queryInfo), infoResp, infoRespSize)) != NX_EOK)
            goto failed;
        for (int i = 0; i < queryInfo.count; i++)
        {
            VirtioSoundResponseQueryJackInfo *jackInfo = &((VirtioSoundResponseQueryJackInfo *) infoResp->data)[i];
            NX_LOG_I("[Jack %d] connected: %s, Features: 0x%X, HDA Default Configuration: 0x%X, HDA Capabilities: 0x%X",
                     i, jackInfo->connected == 1 ? "true" : "false", jackInfo->features, jackInfo->hda_reg_defconf,
                     jackInfo->hda_reg_caps);
        }
    }
    // Read the streams field and send a control request to query information about the available PCM streams.
    if (deviceCfg->streams != 0)
    {
        queryInfo.header.code = VIRTIO_SND_R_PCM_INFO;
        queryInfo.start_id    = 0;
        queryInfo.size        = sizeof(VirtioSoundResponseQueryPCMInfo);
        queryInfo.count       = deviceCfg->streams;
        infoRespSize = queryInfo.size * queryInfo.count + sizeof(VirtioSoundControlHeader);
        infoResp     = NX_MemReAlloc(infoResp, infoRespSize);
        if (infoResp == NX_NULL)
        {
            err = NX_ENOMEM;
            goto failed;
        }
        if ((err     = VirtioSoundSendControlCommand(
                device, VIRTIO_SOUND_QUEUE_CONTROL,
                &queryInfo, sizeof(queryInfo), infoResp, infoRespSize)) != NX_EOK)
            goto failed;
        deviceExtension->pcmStreamsInfo = NX_MemAlloc(sizeof(VirtioSoundPCMInfo) * queryInfo.count);
        if (deviceExtension->pcmStreamsInfo == NX_NULL)
            goto nomem_failed;
        for (int i                      = 0; i < queryInfo.count; i++)
        {
            VirtioSoundResponseQueryPCMInfo *pcmInfoResp = &((VirtioSoundResponseQueryPCMInfo *) infoResp->data)[i];
            VirtioSoundPCMInfo              *pcmInfo     = &pcmInfoResp->pcmInfo;
            NX_MemCopy(&deviceExtension->pcmStreamsInfo[i], pcmInfo, sizeof(VirtioSoundPCMInfo));
            NX_LOG_I(
                    "[PCM Stream %d] Direction: %s, Feature: 0x%X, Supported formats: 0x%llX, Supported framerate: 0x%llX, Channel %d~%d",
                    i, pcmInfo->direction == VIRTIO_SND_D_INPUT ? "input" : "output",
                    pcmInfo->features, pcmInfo->formats, pcmInfo->rates, pcmInfo->channels_min, pcmInfo->channels_max);
        }
    }
    // Read the chmaps field and send a control request to query information about the available channel maps.
    if (deviceCfg->chmaps != 0)
    {
        queryInfo.header.code = VIRTIO_SND_R_CHMAP_INFO;
        queryInfo.start_id    = 0;
        queryInfo.size        = sizeof(VirtioSoundResponseQueryChmapInfo);
        queryInfo.count       = deviceCfg->chmaps;
        infoRespSize = queryInfo.size * queryInfo.count + sizeof(VirtioSoundControlHeader);
        infoResp     = NX_MemReAlloc(infoResp, infoRespSize);
        if (infoResp == NX_NULL)
            goto nomem_failed;
        if ((err   = VirtioSoundSendControlCommand(
                device, VIRTIO_SOUND_QUEUE_CONTROL,
                &queryInfo, sizeof(queryInfo), infoResp, infoRespSize)) != NX_EOK)
            goto failed;
        for (int i = 0; i < queryInfo.count; i++)
        {
            VirtioSoundResponseQueryChmapInfo *chmapInfo = &((VirtioSoundResponseQueryChmapInfo *) infoResp->data)[i];
            NX_LOG_I("[Channel Map %d] Direction: %s", i,
                     chmapInfo->direction == VIRTIO_SND_D_INPUT ? "input" : "output");
        }
    }
    if (infoResp != NX_NULL) NX_MemFree(infoResp);

    // Populate the event queue with empty buffers.
    VirtioSoundEvent *events = NX_MemAlloc(sizeof(VirtioSoundEvent) * CONFIG_NX_DRIVER_VIRTIO_SPLIT_QUEUE_SIZE);
    if (events == NX_NULL)
        NX_LOG_W("No memory for populate the event queue."); // This is not a fatal error for playing
    else
    {
        deviceExtension->events = events;
        VirtioQueue *eventq = VirtioQueueGet(device, VIRTIO_SOUND_QUEUE_EVENT);
        for (int    i       = 0; i < CONFIG_NX_DRIVER_VIRTIO_SPLIT_QUEUE_SIZE; i++)
        {
            NX_U32 idx = VirtioQueueDescAlloc(eventq);
            if (idx == VIRTIO_QUEUE_DESC_INVALID_ID)
                break;
            VirtioQueueDescFill(eventq, idx,
                                (NX_U64) ((NX_UArch) NX_Virt2Phy(&events[i])),
                                sizeof(VirtioSoundEvent),
                                VIRTIO_QUEUE_DESC_FLAG_WRITE, 0);
            VirtioQueuePutIntoAvailRing(eventq, idx);
            eventq->trace[idx].needNotify = NX_True;
        }
        VirtioSendNotification(device, VIRTIO_SOUND_QUEUE_EVENT, VIRTIO_QUEUE_DESC_INVALID_ID);
    }

    return NX_EOK;
nomem_failed:
    err = NX_ENOMEM;
failed:
    VirtioSoundCleanUp(device);
    return err;
}

NX_PRIVATE void VirtioSoundCleanUp(NX_Device *device)
{
    // Reset the device
    VirtioDeviceReset(device);
    // Cleanup VirtioQueue
    for (int i = 0; i < VirtioGetExtension(device)->queue_num; i++)
        VirtioQueueDestroy(device, i);
    VirtioQueueFree(device);

    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    if (deviceExtension == NX_NULL) return;
    if (deviceExtension->deviceConfig != NX_NULL)
        NX_MemFree(deviceExtension->deviceConfig);
    if (deviceExtension->pcmStreamsInfo != NX_NULL)
        NX_MemFree(deviceExtension->pcmStreamsInfo);
    if (deviceExtension->events != NX_NULL)
        NX_MemFree(deviceExtension->events);

    NX_MemFree(deviceExtension);
}

NX_PRIVATE NX_Error open(struct NX_Device *device, NX_U32 flags)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    deviceExtension->deviceStatus = VIRTIO_SOUND_DEVICE_OPENED;
    if (deviceExtension->result_buffer != NX_NULL)
        return NX_EBUSY;
    deviceExtension->result_buffer = NX_FifoCreate(
            CONFIG_NX_DRIVER_VIRTIO_SOUND_EVENT_BUFFER_SIZE * sizeof(VirtioSoundPCMIOStatus));
    if (deviceExtension->result_buffer == NX_NULL)
        return NX_ENOMEM;
    return NX_EOK;
}

NX_PRIVATE NX_Error close(struct NX_Device *device)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    NX_U32                     streamId         = deviceExtension->selectedStreamId;
    if (deviceExtension->deviceStatus == VIRTIO_SOUND_DEVICE_STARTED)
    {
        VirtioSoundPCMStop(device, streamId);
        deviceExtension->deviceStatus = VIRTIO_SOUND_DEVICE_STOPPED;
    }
    if (deviceExtension->deviceStatus == VIRTIO_SOUND_DEVICE_STOPPED)
    {
        VirtioSoundPCMRelease(device, streamId);
        deviceExtension->deviceStatus = VIRTIO_SOUND_DEVICE_RELEASED;
    }
    deviceExtension->deviceStatus               = VIRTIO_SOUND_DEVICE_CLOSED;
    NX_FifoDestroy(deviceExtension->result_buffer);
    deviceExtension->result_buffer = NX_NULL;
    return NX_EOK;
}

NX_PRIVATE NX_Error read(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    buf                                         = VirtioVirt2Phy(buf);
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    NX_U32                     streamId         = deviceExtension->selectedStreamId;
    if (deviceExtension->pcmStreamsInfo[streamId].direction == VIRTIO_SND_D_OUTPUT)
    {
        if (len != sizeof(VirtioSoundPCMIOStatus)) return NX_EINVAL;
        if (NX_FifoLen(deviceExtension->result_buffer) <= 0) return NX_ENORES;
        *outLen = len;
        NX_ASSERT(len == NX_FifoRead(deviceExtension->result_buffer, buf, sizeof(VirtioSoundPCMIOStatus)));
        return NX_EOK;
    }
    else
    {
        // Not support input stream yet.
        return NX_ENOFUNC;
    }
}

NX_PRIVATE NX_Error write(struct NX_Device *device, void *buf, NX_Offset off, NX_Size len, NX_Size *outLen)
{
    buf                                         = VirtioVirt2Phy(buf);
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    NX_U32                     streamId         = deviceExtension->selectedStreamId;
    if (deviceExtension->pcmStreamsInfo[streamId].direction == VIRTIO_SND_D_OUTPUT)
    {
        *outLen = len;
        return VirtioSoundSendTX(device, streamId, buf, len);
    }
    else
    {
        // Not support input stream yet.
        return NX_ENOFUNC;
    }
}

NX_PRIVATE NX_Error control(struct NX_Device *device, NX_U32 cmd, void *arg)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    if (arg == NX_NULL && cmd != VIRTIO_SOUND_CMD_PCM_PREPARE && cmd != VIRTIO_SOUND_CMD_PCM_START &&
        cmd != VIRTIO_SOUND_CMD_PCM_STOP && cmd != VIRTIO_SOUND_CMD_PCM_RELEASE)
        return NX_EINVAL;
    NX_U32 streamId = deviceExtension->selectedStreamId;
    if (arg != NX_NULL &&
        (cmd == VIRTIO_SOUND_CMD_PCM_PREPARE || cmd == VIRTIO_SOUND_CMD_PCM_START || cmd == VIRTIO_SOUND_CMD_PCM_STOP ||
         cmd == VIRTIO_SOUND_CMD_PCM_RELEASE))
        streamId = *((NX_U32 *) arg);
    if (deviceExtension->deviceConfig->streams <= streamId && cmd != VIRTIO_SOUND_CMD_PCM_SELECT_STREAM &&
        cmd != VIRTIO_SOUND_CMD_PCM_SET_PARAM && cmd != VIRTIO_SOUND_CMD_GET_PCM_INFO)
        return NX_EINVAL;
    NX_Error err = NX_EOK;
    switch (cmd)
    {
    case VIRTIO_SOUND_CMD_PCM_SELECT_STREAM:
        streamId = *((NX_U32 *) arg);
        if (deviceExtension->deviceConfig->streams <= streamId)
            return NX_EINVAL;
        deviceExtension->selectedStreamId = streamId;
        break;
    case VIRTIO_SOUND_CMD_PCM_SET_PARAM:
    {
        if (deviceExtension->deviceStatus >= VIRTIO_SOUND_DEVICE_STARTED) return NX_EINVAL;
        VirtioSoundCmdPCMSetParam *param = ((VirtioSoundCmdPCMSetParam *) arg);
        streamId = param->streamId;
        if (deviceExtension->deviceConfig->streams <= streamId)
            return NX_EINVAL;
        deviceExtension->selectedStreamId = streamId;
        VirtioSoundPCMParameters pcmParameters = {
                .features = param->features,
                .channels = param->channels,
                .rate = param->rate,
                .format = param->format,
                .buffer_bytes = param->buffer_bytes,
                .period_bytes = param->period_bytes
        };
        if ((err = VirtioSoundPCMSetParameters(device, streamId, pcmParameters)) != NX_EOK)
        {
            return err;
        }
        else
        {
            deviceExtension->buffer_size = param->buffer_bytes;
            deviceExtension->period_size = param->period_bytes;
        }
    }
    case VIRTIO_SOUND_CMD_PCM_PREPARE:
        if (deviceExtension->deviceStatus == VIRTIO_SOUND_DEVICE_STARTED ||
            deviceExtension->deviceStatus == VIRTIO_SOUND_DEVICE_STOPPED)
            return NX_EINVAL;
        if ((err = VirtioSoundPCMPrepare(device, streamId)) != NX_EOK)
            return err;
        else
        {
            deviceExtension->deviceStatus = VIRTIO_SOUND_DEVICE_PREPARED;
            break;
        }
    case VIRTIO_SOUND_CMD_PCM_START:
        if (deviceExtension->deviceStatus < VIRTIO_SOUND_DEVICE_PREPARED ||
            deviceExtension->deviceStatus > VIRTIO_SOUND_DEVICE_STOPPED)
            return NX_EINVAL;
        if ((err = VirtioSoundPCMStart(device, streamId)) != NX_EOK)
            return err;
        else
        {
            deviceExtension->deviceStatus = VIRTIO_SOUND_DEVICE_STARTED;
            break;
        }
    case VIRTIO_SOUND_CMD_PCM_STOP:
        if (deviceExtension->deviceStatus != VIRTIO_SOUND_DEVICE_STARTED) return NX_EINVAL;
        if ((err = VirtioSoundPCMStop(device, streamId)) != NX_EOK)
            return err;
        else
        {
            deviceExtension->deviceStatus = VIRTIO_SOUND_DEVICE_STOPPED;
            break;
        }
    case VIRTIO_SOUND_CMD_PCM_RELEASE:
        if (deviceExtension->deviceStatus != VIRTIO_SOUND_DEVICE_STOPPED) return NX_EINVAL;
        if ((err = VirtioSoundPCMRelease(device, streamId)) != NX_EOK)
            return err;
        else
        {
            deviceExtension->deviceStatus = VIRTIO_SOUND_DEVICE_RELEASED;
            break;
        }
    case VIRTIO_SOUND_CMD_GET_PCM_INFO:
    {
        VirtioSoundCmdGetPCMInfo *info = ((VirtioSoundCmdGetPCMInfo *) arg);
        streamId = info->streamId;
        if (deviceExtension->deviceConfig->streams <= streamId)
            return NX_EINVAL;
        info->features     = deviceExtension->pcmStreamsInfo[streamId].features;
        info->formats      = deviceExtension->pcmStreamsInfo[streamId].formats;
        info->rates        = deviceExtension->pcmStreamsInfo[streamId].rates;
        info->direction    = deviceExtension->pcmStreamsInfo[streamId].direction;
        info->channels_min = deviceExtension->pcmStreamsInfo[streamId].channels_min;
        info->channels_max = deviceExtension->pcmStreamsInfo[streamId].channels_max;
        break;
    }
    default:
        return NX_EINVAL;
    }
    return NX_EOK;
}

NX_PRIVATE NX_Error poll(struct NX_Device *device, NX_PollState *pState)
{
    VirtioSoundDeviceExtension *deviceExtension = VirtioGetDevExtension(device, VirtioSoundDeviceExtension);
    return NX_FifoPoll(deviceExtension->result_buffer, pState);
}


NX_PRIVATE NX_DriverOps virtioSoundDriverOps = {
        .open = open,
        .close = close,
        .read = read,
        .write = write,
        .control = control,
        .poll = poll
};


NX_PRIVATE VirtioDriverTableEntity virtioSoundDriverTableEntity = {
        .deviceId = VIRTIO_DEVICE_ID_SOUND,
        .driverName = "virtio-sound",
        .deviceNameTemplate = "virtio_sound_%d",
        .deviceType = NX_DEVICE_TYPE_SOUND,
        .driverOps = &virtioSoundDriverOps,
        .initHandler = VirtioSoundInit,
        .cleanUpHandler = VirtioSoundCleanUp
};

VirtioDriverTableEntity *VirtioDriverTableEntityForVirtioSound(void)
{
    return &virtioSoundDriverTableEntity;
}

#else

#include "../virtio.h"

VirtioDriverTableEntity *VirtioDriverTableEntityForVirtioSound(void)
{
    return NX_NULL;
}

#endif // CONFIG_NX_DRIVER_VIRTIO_SOUND
